#!/usr/bin/python3
# -*- coding:UTF-8 -*-

import os
import traceback
import time
import json
import argparse
import re
import requests
import urllib
import urllib3


import AutoExecUtils


class VplexCollector:
    def __init__(self, baseUrl, userName, password, timeout, inspect):
        self.data = {}
        self.baseUrl = baseUrl
        self.cookieJar = requests.cookies.RequestsCookieJar()
        if timeout is None:
            self.timeout = 30
        else:
            self.timeout = timeout
        self.inspect = inspect

        self.initiatorsMap = {}

        self.headers = {'user-agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
                        'Content-type': 'application/json; charset=utf-8',
                        'Accept': 'application/json;format=0;prettyprint=1',
                        'username': userName,
                        'password': password}

    def wwnAddColon(self, wwn):
        pattern = re.compile('.{2}')
        return ':'.join(pattern.findall(wwn))

    def parseTableHeader(self, headerLines, fieldLenArray):
        head = []
        for fieldLen in fieldLenArray:
            head.append('')

        for line in headerLines:
            if line == '':
                continue
            pos = 0
            for k in range(0, len(fieldLenArray)):
                fieldLen = fieldLenArray[k]
                head[k] = head[k] + line[pos:pos+fieldLen].strip() + ' '
                pos = pos + fieldLen

        for k in range(0, len(head)):
            head[k] = head[k].strip()

        return head

    def parseTableBody(self, bodyLines, fieldLenArray):
        body = []
        for line in bodyLines:
            if line == '':
                continue

            record = []
            pos = 0
            for k in range(0, len(fieldLenArray)):
                fieldLen = fieldLenArray[k]
                record.append(line[pos:pos+fieldLen].strip())
                pos = pos + fieldLen

            body.append(record)

        return body

    def parseTable(self, lines):
        lineCount = len(lines)

        fieldLenArray = []
        headerLines = []
        idx = 0
        for idx in range(0, lineCount):
            line = lines[idx]
            headerLines.append(line)
            if re.match(r'^[- ]+$', line):
                for placeholder in line.split('  '):
                    fieldLenArray.append(len(placeholder) + 2)
                headerLines.pop()
                break

        head = self.parseTableHeader(headerLines, fieldLenArray)
        body = self.parseTableBody(lines[idx+1:], fieldLenArray)

        return (head, body)

    def getContext(self, contextDir):
        url = self.baseUrl + '/' + contextDir
        try:
            response = requests.get(url, verify=False, headers=self.headers, cookies=self.cookieJar)
            resObj = response.json()
            return resObj
        except Exception as ex:
            errorMsg = str(ex)
            print("ERROR: ", errorMsg)

    def execCommand(self, command, args):
        tmp = urllib.parse.urlencode({'x': command})
        url = self.baseUrl + '/' + tmp[2:]
        resObj = {}
        try:
            response = requests.post(url, verify=False, data=json.dumps(args), headers=self.headers, cookies=self.cookieJar)
            if response.status_code == 202:
                resultUrl = response.headers['Location']
                statusCode = 517
                while statusCode == 517:
                    res = requests.get(resultUrl, verify=False, headers=self.headers, cookies=self.cookieJar)
                    statusCode = res.status_code
                    time.sleep(10)
                if res.status_code == 200:
                    resObj = res.json()
            elif response.status_code == 200:
                resObj = response.json()
            return resObj
        except Exception as ex:
            errorMsg = str(ex)
            print("ERROR: ", errorMsg)

    # What                             Version         Info
    # -------------------------------  --------------  ------------------------------
    # Product Version                  5.5.2.02.00.03  -
    # SMSv2                            D35.55.0.3.0    -
    # Mgmt Server Base                 D35.55.0.3      -
    # Mgmt Server Software             D35.55.0.5      -
    # Cluster Witness Server Software  D35.55.0.3      Built against GeoSynchrony
    #                                                 version - D35.55.0.3
    def getVersion(self):
        versionRes = self.execCommand('version', {})
        verTxt = versionRes['response']['custom-data']
        verLines = verTxt.split('\n')
        for line in verLines:
            matchObj = re.match(r'Product Version\s+([\d\.]+)', line)
            if matchObj:
                version = matchObj.group(1)
                self.data['VERSION'] = version

    def getEnginesInfo(self):
        enginsObj = self.getContext('/engines')
        engins = enginsObj['response']['context'][0]['children']

        serialNumbers = {}
        enginsArray = []
        for aEngine in engins:
            if aEngine['type'] != 'engine':
                continue

            enginCtx = self.getContext('/engines/' + aEngine['name'])
            enginAttrs = enginCtx['response']['context'][0]['attributes']

            enginInfo = {}
            for enginAttr in enginAttrs:
                enginInfo[enginAttr['name']] = enginAttr['value']
            serialNumbers[enginInfo['top-level-assembly']] = 1
            enginsArray.append(enginInfo)

        serialNumberList = []
        for serialNumber in serialNumbers.keys():
            serialNumberList.append(serialNumber)

        serialNumberList.sort()
        self.data['SN'] = ','.join(serialNumberList)
        self.data['ENGINS'] = enginsArray

    def getClusterNames(self):
        resObj = self.getContext('clusters')
        clusters = resObj['response']['context'][0]['children']
        clusterNames = []
        for cluster in clusters:
            clusterNames.append(cluster['name'])
        return clusterNames

    def getInitiators(self, clusterNames):
        initiatorsMap = {}
        for clusterName in clusterNames:
            initiatorsObj = self.getContext('clusters/' + clusterName + '/exports/initiator-ports')
            initiators = initiatorsObj['response']['context'][0]['children']

            initiatorAttrs = []
            for initiator in initiators:
                initiatorName = initiator['name']
                initiatorObj = self.getContext('clusters/' + clusterName + '/exports/initiator-ports/' + initiatorName)
                initiatorAttrs = initiatorObj['response']['context'][0]['attributes']

                initiatorInfo = {}
                for attr in initiatorAttrs:
                    initiatorInfo[attr['name']] = attr['value']
                initiatorsMap[initiatorInfo['name']] = initiatorInfo

        return initiatorsMap

    def parseSizeStr(self, sizeStr):
        size = sizeStr
        matchObj = re.match(r'(\d+)([MGTP]?)', sizeStr, re.IGNORECASE)
        if matchObj:
            size = float(matchObj.group(1))
            unit = matchObj.group(2).upper()
            if unit == 'M':
                size = round(size / 1000, 2)
            elif unit == 'T':
                size = size * 1000
            elif unit == 'P':
                size = size * 1000 * 1000
        return size

    def getVolumeByStorageViews(self, clusterNames):
        initiatorsMap = self.getInitiators(clusterNames)

        volsMap = {}
        for clusterName in clusterNames:
            viewsObj = self.getContext('clusters/' + clusterName + '/exports/storage-views')
            views = viewsObj['response']['context'][0]['children']

            viewAttrs = []
            for view in views:
                viewName = view['name']
                viewObj = self.getContext('clusters/' + clusterName + '/exports/storage-views/' + viewName)
                viewAttrs = viewObj['response']['context'][0]['attributes']

            viewInfo = {}
            for attr in viewAttrs:
                viewInfo[attr['name']] = attr['value']

            #volsMap[viewInfo['name']] = viewInfo
            for volsLine in viewInfo.get('virtual-volumes'):
                volInfo = {}
                volAttrs = volsLine[1:-1].split(',')
                volName = volAttrs[1]
                volInfo = volsMap.get(volName)
                volSize = self.parseSizeStr(volAttrs[3])

                if volInfo is None:
                    volInfo = {
                        'NAME': volName,
                        'ID': volAttrs[0],
                        'WWN': volAttrs[2],
                        'CAPACITY': volSize,
                        'VISABLE_GROUPS': [],
                        'VISABLE_INITIATORS': []
                    }
                    volsMap[volName] = volInfo

                volInfo['VISABLE_GROUPS'].append(viewInfo['name'])

                for visInitiator in viewInfo.get('initiators'):
                    hbaInfo = initiatorsMap.get(visInitiator)
                    if hbaInfo:
                        volInfo['VISABLE_INITIATORS'].append(
                            {
                                'NAME': hbaInfo['name'],
                                'WWNN': self.wwnAddColon(hbaInfo['node-wwn'][2:]),
                                'WWPN': self.wwnAddColon(hbaInfo['port-wwn'][2:]),
                                'TYPE': hbaInfo['type']
                            }
                        )

            luns = []
            for k, v in volsMap.items():
                luns.append(v)
            self.data['LUNS'] = luns

        return volsMap

    def getStorageVolumeSummary(self, clusterNames):
        unhealthVols = []
        summarys = []

        for clusterName in clusterNames:
            storVolSummaryRes = self.execCommand('storage-volume summary', {'args': '--clusters ' + clusterName})
            cmdOut = storVolSummaryRes['response']['custom-data']
            outLines = cmdOut.split('\n')
            idx = 0
            for idx in range(0, len(outLines)):
                line = outLines[idx]
                if re.match(r'Storage-Volume Summary', line):
                    break
            if idx > 0:
                (head, body) = self.parseTable(outLines[0:idx])
                for record in body:
                    unhealthVolInfo = {}
                    for k in range(0, len(head)):
                        if head[k] == 'StorageVolume Name':
                            unhealthVolInfo['NAME'] = record[k]
                        elif head[k] == 'IO Status':
                            unhealthVolInfo['IO_STATUS'] = record[k]
                        elif head[k] == 'Operational Status':
                            unhealthVolInfo['OPER_STATUS'] = record[k]
                        elif head[k] == 'Health State':
                            unhealthVolInfo['HEALTH_STATE'] = record[k]
                    unhealthVols.append(unhealthVolInfo)

            (head, body) = self.parseTable(outLines[idx:])
            summaryInfo = {}
            summaryInfo['CLUSTER_NAME'] = clusterName
            vendorInfos = []
            summaryInfo['VENDOR'] = vendorInfos
            preFType = None
            for record in body:
                fType = record[0]
                subSegs = re.split('\s+', record[1])
                if fType == '':
                    fType = preFType
                if fType == 'Vendor':
                    vendorInfos.append({'NAME': subSegs[0], 'VOLUME_COUNT': int(subSegs[1])})
                elif fType == 'Capacity':
                    summaryInfo['CAPACITY'] = subSegs[1]
                else:
                    summaryInfo[subSegs[0].upper()] = int(subSegs[1])

            summarys.append(summaryInfo)

        self.data['STORAGE_VOLUMES_UNHEALTH'] = unhealthVols
        self.data['STORAGE_VOLUME_SUMMARY'] = summarys

    def getStorageArrays(self, clusterNames):
        storArrayInfos = []
        for clusterName in clusterNames:
            storArraysObj = self.getContext('clusters/' + clusterName + '/storage-elements/storage-arrays')
            storArrays = storArraysObj['response']['context'][0]['children']

            storArrayAttrs = []
            for storArray in storArrays:
                storArrayName = storArray['name']
                storArrayObj = self.getContext('clusters/' + clusterName + '/storage-elements/storage-arrays/' + storArrayName)
                storArrayAttrs = storArrayObj['response']['context'][0]['attributes']

            storArrayInfo = {}
            for attr in storArrayAttrs:
                storArrayInfo[attr['name'].upper()] = attr['value']
            storArrayInfos.append(storArrayInfo)

        self.data['STORAGE_ARRAYS'] = storArrayInfos

        return storArrayInfos

    def healthCheck(self):
        healthRes = self.execCommand('health-check', {'args': '--highlevel'})
        self.data['HEALTH_CHECK'] = healthRes['response']['custom-data']

    def collect(self):
        # self.getVersion()
        print("INFO: Try to collect engine information.\n")
        self.getEnginesInfo()

        print("INFO: Try to collect cluster config information.\n")
        clusterNames = self.getClusterNames()

        print("INFO: Try to collect volumes information.\n")
        self.getVolumeByStorageViews(clusterNames)

        print("INFO: Try to collect storage arrays information.\n")
        self.getStorageArrays(clusterNames)

        print("INFO: Try to collect volumes summary information.\n")
        self.getStorageVolumeSummary(clusterNames)

        if self.inspect == 1:
            print("INFO: Try to do health check.\n")
            self.healthCheck()

        self.data['_OBJ_CATEGORY'] = 'STORAGE'
        self.data['_OBJ_TYPE'] = 'Virtual_Storage'
        self.data['BRAND'] = 'EMC'
        self.data['VENDOR'] = 'EMC'
        self.data['MODEL'] = 'VPLEX'
        self.data['APP_TYPE'] = 'VPLEX'
        self.data['PK'] = ['MGMT_IP']

        print("INFO: Information collcted.\n")
        return self.data


def usage():
    pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--node', default='', help='Execution node json')
    parser.add_argument('--verbose', default=0, help='Verbose')
    parser.add_argument('--timeout', default=10, help='Timeout seconds')
    parser.add_argument('--inspect', default=0, help='Health check')
    parser.add_argument('otherthings', nargs=argparse.REMAINDER)

    args = parser.parse_args()

    inspect = int(args.inspect)
    timeOut = int(args.timeout)
    verbose = int(args.verbose)

    if timeOut == 0:
        timeOut = 5

    node = args.node

    try:
        nodeInfo = {}
        hasOptError = False
        if node is None or node == '':
            node = os.getenv('AUTOEXEC_NODE')
        if node is None or node == '':
            print("ERROR: Can not find node definition.")
            hasOptError = True
        else:
            nodeInfo = json.loads(node)

        if hasOptError:
            usage()

        hasError = False

        ip = nodeInfo['host']
        #port = nodeInfo['protocolPort']
        username = nodeInfo['username']
        password = nodeInfo['password']

        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        vplexCollector = VplexCollector('https://' + ip + '/vplex', username, password, timeOut, inspect)
        data = vplexCollector.collect()
        data['RESOURCE_ID'] = nodeInfo.get('resourceId')
        data['MGMT_IP'] = nodeInfo.get('host')
        out = {'DATA': [data]}
        AutoExecUtils.saveOutput(out)
        if verbose == 1:
            print(json.dumps(data, ensure_ascii=True, indent=4))
    except Exception as ex:
        errorMsg = str(ex)
        print("ERROR: ", errorMsg)
        traceback.print_exc()
